//------------------------------------------------------------
//  Filename: vga_dma_M00_AXI.v
//   
//  Author  : wlduan@gmail.com
//  Revise  : 2016-10-07 17:12
//  Description: 
//   
//  Copyright (C) 2014, YRBD, Inc. 					      
//  All Rights Reserved.                                       
//-------------------------------------------------------------
//
`timescale 1ns/1ps
 
module VGA_DMA_M00_AXI #( 
    parameter integer C_M_AXI_BURST_LEN      = 16,
    parameter integer C_M_AXI_ID_WIDTH       = 1,
    parameter integer C_M_AXI_ADDR_WIDTH     = 32,
    parameter integer C_M_AXI_DATA_WIDTH     = 32,
    parameter integer C_M_AXI_AWUSER_WIDTH   = 0,
    parameter integer C_M_AXI_ARUSER_WIDTH   = 0,
    parameter integer C_M_AXI_WUSER_WIDTH    = 0,
    parameter integer C_M_AXI_RUSER_WIDTH    = 0,
    parameter integer C_M_AXI_BUSER_WIDTH    = 0
)( 
    //************* Write channel ***********
    input  wire                               M_AXI_ACLK,
    input  wire                               M_AXI_ARESETN,
    output wire [C_M_AXI_ID_WIDTH-1 : 0     ] M_AXI_AWID,
    output wire [C_M_AXI_ADDR_WIDTH-1 : 0   ] M_AXI_AWADDR,
    output wire [7 : 0                      ] M_AXI_AWLEN,
    output wire [2 : 0                      ] M_AXI_AWSIZE,
    output wire [1 : 0                      ] M_AXI_AWBURST,
    output wire                               M_AXI_AWLOCK,
    output wire [3 : 0                      ] M_AXI_AWCACHE,
    output wire [2 : 0                      ] M_AXI_AWPROT,
    output wire [3 : 0                      ] M_AXI_AWQOS,
    output wire [C_M_AXI_AWUSER_WIDTH-1 : 0 ] M_AXI_AWUSER,
    output wire                               M_AXI_AWVALID,
    input  wire                               M_AXI_AWREADY,
    output wire [C_M_AXI_DATA_WIDTH-1 : 0   ] M_AXI_WDATA,
    output wire [C_M_AXI_DATA_WIDTH/8-1 : 0 ] M_AXI_WSTRB,
    output wire                               M_AXI_WLAST,
    output wire [C_M_AXI_WUSER_WIDTH-1 : 0  ] M_AXI_WUSER,
    output wire                               M_AXI_WVALID,
    input  wire                               M_AXI_WREADY,
    input  wire [C_M_AXI_ID_WIDTH-1 : 0     ] M_AXI_BID,
    input  wire [1 : 0                      ] M_AXI_BRESP,
    input  wire [C_M_AXI_BUSER_WIDTH-1 : 0  ] M_AXI_BUSER,
    input  wire                               M_AXI_BVALID,
    output wire                               M_AXI_BREADY,
    //************* Read channel ***********
    output wire [C_M_AXI_ID_WIDTH-1 : 0     ] M_AXI_ARID,
    output reg  [C_M_AXI_ADDR_WIDTH-1 : 0   ] M_AXI_ARADDR,
    output wire [7 : 0                      ] M_AXI_ARLEN,
    output wire [2 : 0                      ] M_AXI_ARSIZE,
    output wire [1 : 0                      ] M_AXI_ARBURST,
    output wire                               M_AXI_ARLOCK,
    output wire [3 : 0                      ] M_AXI_ARCACHE,
    output wire [2 : 0                      ] M_AXI_ARPROT,
    output wire [3 : 0                      ] M_AXI_ARQOS,
    output wire [C_M_AXI_ARUSER_WIDTH-1 : 0 ] M_AXI_ARUSER,
    output reg                                M_AXI_ARVALID,
    input  wire                               M_AXI_ARREADY,
    input  wire [C_M_AXI_ID_WIDTH-1 : 0     ] M_AXI_RID,
    input  wire [C_M_AXI_DATA_WIDTH-1 : 0   ] M_AXI_RDATA,
    input  wire [1 : 0                      ] M_AXI_RRESP,
    input  wire                               M_AXI_RLAST,
    input  wire [C_M_AXI_RUSER_WIDTH-1 : 0  ] M_AXI_RUSER,
    input  wire                               M_AXI_RVALID,
    output wire                               M_AXI_RREADY,
    //************** fifo interface **********************
    input  wire                               disp_rd_clk,
    input  wire                               disp_rd_en,
    output wire                               disp_empty,
    output wire [32:0                       ] disp_dout,
    output wire [32:0                       ] menu_dout,
    //************** config regs *************************
    input  wire                               video0_only,
    input  wire                               video1_only,
    input  wire [31:0                       ] video0_base_addr,
    input  wire [31:0                       ] video1_base_addr,
    input  wire [31:0                       ] menu_base_addr,
    output reg  [1:0                        ] disp_flow_err,   
    output reg  [1:0                        ] menu_flow_err,   
    input  wire [31:0                       ] pix_cnt,
    input  wire                               vga_v_sync,
    input  wire                               mm_disp_en,   
    input  wire                               mm_menu_en   
);  
//--------------------------------------------------------
wire clk = M_AXI_ACLK;
wire rst = ~M_AXI_ARESETN;

wire frame_last;
wire prog_full;
wire prog_empty;
//--------------------------------------------------------
parameter   IDLE        = 8'b0000_0001;
parameter   DISP_ADDR   = 8'b0000_0010;  
parameter   DISP_DATA   = 8'b0000_0100;  
parameter   WAIT_FIFO   = 8'b0000_1000;  
parameter   NEXT_READ   = 8'b0001_0000;  
parameter   NEXT_FRAME  = 8'b0010_0000; 
parameter   MENU_ADDR   = 8'b0100_0000; 
parameter   MENU_DATA   = 8'b1000_0000; 
//--------------------------------------------------------
reg [7:0]   cur_state;  
reg [7:0]   nxt_state;  
//--------------------------------------------------------
reg vga_v_sync_ff1;
reg vga_v_sync_ff2;
always @(posedge clk) vga_v_sync_ff1 <= vga_v_sync;
always @(posedge clk) vga_v_sync_ff2 <= vga_v_sync_ff1;
//--------------------------------------------------------
reg new_frm;
always @(posedge clk) new_frm <= vga_v_sync_ff2&(~vga_v_sync_ff1); //neg edge
//--------------------------------------------------------
always @(posedge clk,posedge rst) begin
    if(rst)begin 
        cur_state <= IDLE;  
    end  
    else  begin  
        cur_state <= nxt_state;  
    end  
end  
//--------------------------------------------------------
always @(*) begin  
    case(cur_state)  
        IDLE: begin  
            if(new_frm&mm_disp_en)  
                nxt_state  = (mm_menu_en)? MENU_ADDR:DISP_ADDR;  
            else  
                nxt_state  = cur_state;  
        end  
        MENU_ADDR: begin  
            if(M_AXI_ARREADY & M_AXI_ARVALID)  
                nxt_state  = MENU_DATA;  
            else  
                nxt_state  = cur_state;  
        end  
        MENU_DATA: begin  
            if(M_AXI_RREADY & M_AXI_RVALID & M_AXI_RLAST)   
                nxt_state  = DISP_ADDR;  
            else  
                nxt_state  = cur_state;
        end  
        DISP_ADDR: begin
            if(M_AXI_ARREADY & M_AXI_ARVALID)  
                nxt_state  = DISP_DATA;  
            else  
                nxt_state  = cur_state;          
        end
        DISP_DATA: begin
            if(M_AXI_RREADY & M_AXI_RVALID & M_AXI_RLAST & frame_last)  
                nxt_state  = NEXT_FRAME;  
            else if(M_AXI_RREADY & M_AXI_RVALID & M_AXI_RLAST & prog_full) 
                nxt_state  = WAIT_FIFO;  
            else if(M_AXI_RREADY & M_AXI_RVALID & M_AXI_RLAST)   
                nxt_state  = NEXT_READ;  
            else  
                nxt_state  = cur_state;          
        end
        WAIT_FIFO: begin  
            if(frame_last)
                nxt_state  = NEXT_FRAME;  
            else if(~prog_full)
                nxt_state  = NEXT_READ;  
            else  
                nxt_state  = cur_state;  
        end  
        NEXT_READ: begin  
            nxt_state  = (mm_menu_en)? MENU_ADDR:DISP_ADDR;  
        end  
        NEXT_FRAME: begin  
            nxt_state  = IDLE;//DISP_ADDR  
        end  
        default: begin  
            nxt_state  = cur_state;  
        end  
    endcase  
end  
//--------------------------------------------------------
reg[31:0] inner_frm_addr;
//--------------------------------------------------------
always @(posedge clk,posedge rst) begin
    if(rst)begin 
        inner_frm_addr <= 32'b0;    
    end 
    else if((cur_state == DISP_DATA)&&(M_AXI_RVALID)&&(M_AXI_RREADY)) begin 
        inner_frm_addr <= inner_frm_addr + 32'b1;    
    end
    else if(new_frm)begin
        inner_frm_addr <= 32'b0;    
    end
end 
//--------------------------------------------------------
reg[31:0] cur_cam0_addr;
reg[31:0] cur_cam1_addr;
reg[31:0] cur_menu_addr;
//--------------------------------------------------------
always @(posedge clk,posedge rst) begin
    if(rst)begin 
        cur_cam0_addr <= 32'h0;    
        cur_cam1_addr <= 32'h0;    
        cur_menu_addr <= 32'h0;    
    end 
    else if(cur_state == IDLE) begin
        cur_cam0_addr <= video0_base_addr;    
        cur_cam1_addr <= video1_base_addr;    
        cur_menu_addr <= menu_base_addr;    
    end 
end 
//--------------------------------------------------------
reg[15:0] pix_integer;
//--------------------------------------------------------
always @(posedge clk,posedge rst) begin
    if(rst)begin 
        M_AXI_ARADDR  <= 'b0;
        M_AXI_ARVALID <= 1'b0;
        pix_integer   <= 16'b0;
    end 
    else if(cur_state == MENU_ADDR ) begin 
        // Once asserted, VALIDs cannot be deasserted, so axi_arvalid
        // must wait until transaction is accepted  
        M_AXI_ARADDR  <= cur_menu_addr + {inner_frm_addr[29:0],2'b0};
        M_AXI_ARVALID <= (M_AXI_ARVALID&M_AXI_ARREADY)?1'b0:1'b1;
    end
    else if(cur_state == DISP_ADDR ) begin
        if( video0_only )begin
            M_AXI_ARADDR  <= cur_cam0_addr + {inner_frm_addr[29:0],2'b0};
        end
        else if( video1_only ) begin
            M_AXI_ARADDR  <= cur_cam1_addr + {inner_frm_addr[29:0],2'b0};
        end
        else if(inner_frm_addr[29:0] < pix_cnt[31:1]) begin
            M_AXI_ARADDR  <= cur_cam0_addr + {inner_frm_addr[29:0],2'b0};
        end
        else begin
            M_AXI_ARADDR  <= cur_cam1_addr + {(inner_frm_addr[29:0] - pix_cnt[31:1]),2'b0};
        end
        M_AXI_ARVALID <= (M_AXI_ARVALID&M_AXI_ARREADY)?1'b0:1'b1;    
    end
    else begin
        M_AXI_ARVALID <= 1'b0;  
    end
end   
//--------------------------------------------------------
wire video0_in_proc    = (~video0_only)&&(~video0_only)&&(inner_frm_addr[29:0] < pix_cnt[31:1]);
wire video1_in_proc    = (~video0_only)&&(~video0_only)&&(inner_frm_addr[29:0] >= pix_cnt[31:1]);
wire[31:0] cur_pix_cnt = inner_frm_addr + 2;
assign frame_last      = (cur_pix_cnt >  pix_cnt)?1'b1:1'b0;
assign M_AXI_RREADY    = ((cur_state == DISP_DATA)||(cur_state == MENU_DATA))?1'b1:1'b0;
//--------------------------------------------------------
wire[32:0 ] disp_din   = {frame_last,M_AXI_RDATA};
wire[32:0 ] menu_din   = {frame_last,M_AXI_RDATA};
wire        disp_wr_en = (cur_state == DISP_DATA)&&M_AXI_RVALID&&M_AXI_RREADY;
wire        menu_wr_en = (cur_state == MENU_DATA)&&M_AXI_RVALID&&M_AXI_RREADY;
wire        underflow0  ;
wire        overflow0   ;
wire        underflow1  ;
wire        overflow1   ;
//--------------------------------------------------------
fifo_frm_buffer_33x fifo_inst0 (   
  .rst           ( rst                ) ,
  
  .wr_clk        ( clk                ) ,    
  .din           ( disp_din           ) ,
  .wr_en         ( disp_wr_en         ) ,
  .prog_full     ( prog_full          ) ,
  .prog_empty    (                    ) ,
  .underflow     ( underflow0         ) ,
  .overflow      ( overflow0          ) ,

  .rd_clk        ( disp_rd_clk        ) ,
  .rd_en         ( disp_rd_en         ) ,
  .dout          ( disp_dout          ) ,
  .empty         ( disp_empty         ) 
); 
//--------------------------------------------------------
fifo_frm_buffer_33x fifo_inst1 (   
  .rst           ( rst                ) ,
  
  .wr_clk        ( clk                ) ,
  .din           ( menu_din           ) ,
  .wr_en         ( menu_wr_en         ) ,
  .prog_full     (                    ) ,
  .prog_empty    (                    ) ,
  .underflow     ( underflow1         ) ,
  .overflow      ( overflow1          ) ,

  .rd_clk        ( disp_rd_clk        ) ,
  .rd_en         ( disp_rd_en         ) ,
  .dout          ( menu_dout          ) ,
  .empty         (                    ) 
); 
//--------------------------------------------------------
always @(posedge clk,posedge rst) begin
    if(rst)begin
        disp_flow_err <= 2'b0;
        menu_flow_err <= 2'b0;
    end
    else begin
        disp_flow_err <= {overflow0,underflow0};
        menu_flow_err <= {overflow1,underflow1};
    end
end
//--------------------------------------------------------
function integer clogb2 (input integer bit_depth);              
    begin                                                           
    for(clogb2=0; bit_depth>0; clogb2=clogb2+1)                   
        bit_depth = bit_depth >> 1;                                 
    end                                                           
endfunction   
//--------------------------------------------------------
assign M_AXI_AWID      = 'b0;
assign M_AXI_AWLEN	   = C_M_AXI_BURST_LEN - 1;            //burst length: 16  
assign M_AXI_AWSIZE	   = clogb2((C_M_AXI_DATA_WIDTH/8)-1); //Size should be C_M_AXI_DATA_WIDTH, in 2^SIZE bytes
assign M_AXI_AWBURST   = 2'b01     ;                       //00:fix,01:incr,10:wrap,11:rsv 
assign M_AXI_AWLOCK    = 2'b00     ;   //
assign M_AXI_AWCACHE   = 4'b0011   ;   /////  
assign M_AXI_AWPROT    = 3'b000    ;   // data trans
assign M_AXI_AWQOS     = 4'b0000   ;   //
assign M_AXI_AWUSER	   = 'b1       ;

assign M_AXI_WSTRB	   = {(C_M_AXI_DATA_WIDTH/8){1'b1}};
assign M_AXI_WUSER	   = 'b0;

assign M_AXI_ARID	   = 'b0;
assign M_AXI_ARLEN	   = C_M_AXI_BURST_LEN - 1;
assign M_AXI_ARSIZE    = clogb2((C_M_AXI_DATA_WIDTH/8)-1);
assign M_AXI_ARBURST   = 2'b01;
assign M_AXI_ARLOCK	   = 2'b0;
assign M_AXI_ARCACHE   = 4'b0011;
assign M_AXI_ARPROT	   = 3'h0;
assign M_AXI_ARQOS	   = 4'h0;
assign M_AXI_ARUSER	   = 'b1;

assign M_AXI_BREADY    = 'b1;

endmodule
